package robocup.component.geometry.analysers;

import java.util.LinkedList;

import robocup.component.SConf;
import robocup.component.actions.KickAction;
import robocup.component.geometry.Circle;
import robocup.component.geometry.CircleSegment;
import robocup.component.geometry.LineSegment;
import robocup.component.geometry.StraightLine;
import robocup.component.geometry.Vektor;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.RELATIVE;

/**
 * @author Thomas Karbe
 * 
 * This class represents all possible ball positions after a tackling or a kick
 * with a given acceleration angle. It uses LineSegments and CircleSegments to
 * represent the possible positions and makes it possible, to calculate with
 * geometry.
 */
public class PossibleBallsAfterDirectedAcc {

  final private Ball          ball           = Ball.getDummyInstance();
  final private Player        shootingPlayer = Player.getDummyInstance();
  private ACTION_TYPE         actionType;
  final private LineSegment   line           = LineSegment.getDummyInstance();
  final private CircleSegment circ1          = CircleSegment.getDummyInstance();
  final private CircleSegment circ2          = CircleSegment.getDummyInstance();
  final private Vektor        dummyVektor    = Vektor.getDummyInstance();
  final private LineSegment   dummyLine      = LineSegment.getDummyInstance();

  /**
   * positive end of line and start of circ1 reached with this power
   */
  private double              borderPower1   = 100.;
  /**
   * negative end of line and start of circ2 reached with this power
   */
  private double              borderPower2   = -100.;
  
  private double              kickDir        = 0.;

  /**
   * marks if borderAngle1 is startAngle of circ1 (true) or not (false)
   */
  private boolean             circ1Starts    = true;
  /**
   * marks if borderAngle2 is startAngle of circ2 (true) or not (false)
   */
  private boolean             circ2Starts    = true;
  
  /**
   * positive end of line and start of circ1 at this angle from ball
   */
  private double              borderAngle1   = 0;
  /**
   * negative end of line and start of circ2 at this angle from ball
   */
  private double              borderAngle2   = 0;
  
  private int                 simulatedCycle = 1;
  private double              minAng;
  private double              maxAng;

  private Ball                dummyBall      = Ball.getDummyInstance();
  private KickAction          kick           = new KickAction(0, 0);
  // private TackleAction        tackle         = new TackleAction(0);

  private enum ACTION_TYPE {
    /**
     * a tackle action
     */
    TACKLE_ACTION,
    /**
     * a kick action
     */
    KICK_ACTION
  }

  /**
   * Creates an uninitialized object.
   */
  public PossibleBallsAfterDirectedAcc() {

    calculateValues();
  }

  /**
   * Creates an object based on possible TackleActions.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   */
  public PossibleBallsAfterDirectedAcc(
      final Player p,
      final Ball b) {

    ball.copy(b);
    shootingPlayer.copy(p);
    Vektor pos = p.getPosRel(b, RELATIVE.TO_BODY);
    if (Math.abs(pos.x) < SConf.getInstance().TACKLE_WIDTH
        && pos.y < SConf.getInstance().TACKLE_DIST) {
      actionType = ACTION_TYPE.TACKLE_ACTION;
      kickDir = p.getBodyDir();
    }
    else {
      actionType = null;
    }
    calculateValues();
  }

  /**
   * Creates an object based on possible KickActions withe the given angle of
   * the KickAction.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param kick
   *          the KickAction that delivers the kickAngle (power stays variable)
   */
  public PossibleBallsAfterDirectedAcc(
      final Player p,
      final Ball b,
      final KickAction kick) {

    ball.copy(b);
    shootingPlayer.copy(p);
    if (p.canKick(b)) {
      actionType = ACTION_TYPE.KICK_ACTION;
      kickDir = Vektor.normalize(p.getBodyDir() + kick.getDirection());
    }
    else {
      actionType = null;
    }
    calculateValues();
  }

  /**
   * Creates an object based on possible KickActions with the the given angle.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param a
   *          the the kickAngle (power stays variable)
   */
  public PossibleBallsAfterDirectedAcc(
      final Player p,
      final Ball b,
      final double a) {

    ball.copy(b);
    shootingPlayer.copy(p);
    if (p.canKick(b)) {
      actionType = ACTION_TYPE.KICK_ACTION;
      kickDir = Vektor.normalize(p.getBodyDir() + a);
    }
    else {
      actionType = null;
    }
    calculateValues();
  }

  /**
   * Copies the values of acc into this object.
   * 
   * @param acc
   *          the PossibleBalls to copy
   */
  public PossibleBallsAfterDirectedAcc(
      final PossibleBallsAfterDirectedAcc acc) {

    actionType = acc.actionType;
    ball.copy(acc.ball);
    borderAngle1 = acc.borderAngle1;
    borderAngle2 = acc.borderAngle2;
    borderPower1 = acc.borderPower1;
    borderPower2 = acc.borderPower2;
    circ1.copy(acc.circ1);
    circ2.copy(acc.circ2);
    line.copy(acc.line);
    circ1Starts = acc.circ1Starts;
    circ2Starts = acc.circ2Starts;
    kickDir = acc.kickDir;
    shootingPlayer.copy(acc.shootingPlayer);
    simulatedCycle = acc.simulatedCycle;
    minAng = acc.minAng;
    maxAng = acc.maxAng;
  }

  /**
   * Creates an object based on possible TackleActions.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   */
  public final void setParams(final Player p,
      final Ball b) {

    ball.copy(b);
    shootingPlayer.copy(p);
    if (p.canKick(b)) {
      actionType = ACTION_TYPE.TACKLE_ACTION;
      kickDir = p.getBodyDir();
    }
    else {
      actionType = null;
    }
    simulatedCycle = 1;
    calculateValues();
  }

  /**
   * Creates an object based on possible KickActions withe the given angle of
   * the KickAction.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param kick
   *          the KickAction that delivers the kickAngle (power stays variable)
   */
  public final void setParams(final Player p,
      final Ball b,
      final KickAction kick) {

    ball.copy(b);
    shootingPlayer.copy(p);
    if (p.canKick(b)) {
      actionType = ACTION_TYPE.KICK_ACTION;
      kickDir = Vektor.normalize(p.getBodyDir() + kick.getDirection());
    }
    else {
      actionType = null;
    }
    simulatedCycle = 1;
    calculateValues();
  }

  /**
   * Creates an object based on possible KickActions withe the given angle.
   * 
   * @param p
   *          the player
   * @param b
   *          the ball
   * @param a
   *          the the kickAngle (power stays variable)
   */
  public final void setParams(final Player p,
      final Ball b,
      final double a) {

    ball.copy(b);
    shootingPlayer.copy(p);
    if (p.canKick(b)) {
      actionType = ACTION_TYPE.KICK_ACTION;
      kickDir = Vektor.normalize(p.getBodyDir() + a);
    }
    else {
      actionType = null;
    }
    simulatedCycle = 1;
    calculateValues();
  }

  private final void calculateValues() {

    if (actionType == null) {
      line.reset();
      circ1.reset();
      circ2.reset();
      borderPower1 = 100.;
      borderPower2 = -100.;
      kickDir = 0.;
      circ1Starts = true;
      circ2Starts = true;
      borderAngle1 = 0;
      borderAngle2 = 0;
      minAng = -180;
      maxAng = 180;
      return;
    }
    if (actionType == ACTION_TYPE.KICK_ACTION) {
      borderPower2 = 0;
      circ2.reset();
      double spd_x = ball.getSpeed().x;
      double spd_y = ball.getSpeed().y;
      double cosAlpha = Math.cos(Math.toRadians(kickDir));
      double sinAlpha = Math.sin(Math.toRadians(kickDir));
      double kpr = SConf.getInstance().KICK_POWER_RATE;
      double maxSpd = SConf.getInstance().BALL_SPEED_MAX;
      double powerReduction = 1
          - Math.abs(shootingPlayer.getAngleRel(ball, RELATIVE.TO_BODY) / 720)
          - 0.25
          * ((shootingPlayer.getDistance(ball) - SConf.getInstance().BALL_SIZE - shootingPlayer
              .getPConf().PLAYER_SIZE) / shootingPlayer.getPConf().KICKABLE_MARGIN);
      double base = -((spd_x * sinAlpha + spd_y * cosAlpha) / kpr);
      double det = Math.sqrt(base
          * base
          - ((spd_x * spd_x + spd_y * spd_y - maxSpd * maxSpd) / (kpr * kpr)));
      double res = (base + det) / powerReduction;
      borderPower1 = Math.min(Math.max(0, res), 100);
      if (borderPower1 < 100) {
        circ1.copyCenter(ball.getPosition());
        circ1.setRadius(SConf.getInstance().BALL_SPEED_MAX);

        dummyBall.copy(ball);
        kick.setPower(borderPower1);
        kick.setDirection(kickDir - shootingPlayer.getBodyDir());
        dummyBall.predictThisAfterAction(shootingPlayer, kick);
        double angle1 = ball.getAngleTo(dummyBall);
        borderAngle1 = angle1;
        circ1.setStartAngle(angle1);
        line.copyPoint2(dummyBall.getPosition());

        dummyBall.copy(ball);
        kick.setPower(100);
        dummyBall.predictThisAfterAction(shootingPlayer, kick);
        double angle2 = ball.getAngleTo(dummyBall);
        if (circ1.getDistToAngle(angle2) < 180) {
          circ1.setEndAngle(angle2);
          circ1Starts = true;
        }
        else {
          circ1.setStartAngle(angle2);
          circ1.setEndAngle(angle1);
          circ1Starts = false;
        }
        dummyBall.copy(ball);

        dummyBall.extrapolate(1);
        line.copyPoint1(dummyBall.getPosition());
      }
      else {
        circ1.reset();

        dummyBall.copy(ball);
        kick.setPower(borderPower1);
        kick.setDirection(shootingPlayer.getAngleRel(kickDir, RELATIVE.TO_BODY));
        dummyBall.predictThisAfterAction(shootingPlayer, kick);
        borderAngle1 = ball.getAngleTo(dummyBall);
        line.copyPoint2(dummyBall.getPosition());

        dummyBall.copy(ball);
        dummyBall.extrapolate(1);
        line.copyPoint1(dummyBall.getPosition());
      }
      calcMinMaxAng();
      return;
    }
    // tackle action
//    double spd_x = ball.getSpeed().x;
//    double spd_y = ball.getSpeed().y;
//    double cosAlpha = Math.cos(Math.toRadians(kickDir));
//    double sinAlpha = Math.sin(Math.toRadians(kickDir));
//    double tpr = SConf.getInstance().TACKLE_POWER_RATE;
//    double maxSpd = SConf.getInstance().BALL_SPEED_MAX;
//    double base = -((spd_x * sinAlpha + spd_y * cosAlpha) / tpr);
//    double det = Math.sqrt(base
//        * base
//        - ((spd_x * spd_x + spd_y * spd_y - maxSpd * maxSpd) / (tpr * tpr)));
//    double res1 = base + det;
//    double res2 = base - det;
//    borderPower1 = Math.max(Math.min(res1, 100), 0);
//    borderPower2 = Math.max(Math.min(res2, 0), -100);
//    if (borderPower1 < 100) {
//      circ1.copyCenter(ball.getPosition());
//      circ1.setRadius(SConf.getInstance().BALL_SPEED_MAX);
//
//      dummyBall.copy(ball);
//      // TODO: is that correct
//      tackle.setDirection(0);
//      dummyBall.predictThisAfterAction(shootingPlayer, tackle);
//      double angle1 = ball.getAngleTo(dummyBall);
//      borderAngle1 = angle1;
//
//      dummyBall.copy(ball);
//      // TODO: is that correct
//      tackle.setDirection(0);
//      dummyBall.predictThisAfterAction(shootingPlayer, tackle);
//      double angle2 = ball.getAngleTo(dummyBall);
//      double dist = Vektor.getAngleDistance(angle1, angle2);
//      circ1.setStartAngle(dist > 180 ? angle2 : angle1);
//      circ1.setEndAngle(dist > 180 ? angle1 : angle2);
//      circ1Starts = dist > 180 ? false : true;
//    }
//    else {
//      circ1.reset();
//    }
//    if (borderPower2 > -100) {
//      circ2.copyCenter(ball.getPosition());
//      circ2.setRadius(SConf.getInstance().BALL_SPEED_MAX);
//
//      dummyBall.copy(ball);
//      // TODO: 
//      tackle.setDirection(0);
//      dummyBall.predictThisAfterAction(shootingPlayer, tackle);
//      double angle1 = ball.getAngleTo(dummyBall);
//      borderAngle2 = angle1;
//
//      dummyBall.copy(ball);
//      // TODO: 
//      tackle.setDirection(0);
//      dummyBall.predictThisAfterAction(shootingPlayer, tackle);
//      double angle2 = ball.getAngleTo(dummyBall);
//      double dist = Vektor.getAngleDistance(angle1, angle2);
//      circ2.setStartAngle(dist > 180 ? angle2 : angle1);
//      circ2.setEndAngle(dist > 180 ? angle1 : angle2);
//      circ2Starts = dist > 180 ? false : true;
//    }
//    else {
//      circ2.reset();
//    }
//
//    dummyBall.copy(ball);
//    // TODO: 
//    tackle.setDirection(0);
//    dummyBall.predictThisAfterAction(shootingPlayer, tackle);
//    line.copyPoint1(dummyBall.getPosition());
//    if (borderPower2 <= -100) {
//      borderAngle2= ball.getAngleTo(dummyBall);
//    }
//
//    dummyBall.copy(ball);
//    // TODO:
//    tackle.setDirection(borderPower1);
//    dummyBall.predictThisAfterAction(shootingPlayer, tackle);
//    line.copyPoint2(dummyBall.getPosition());
//    if (borderPower1 >= 100) {
//      borderAngle1= ball.getAngleTo(dummyBall);
//    }
    calcMinMaxAng();
  }

  /**
   * Returns the ball
   * @return the ball
   */
  public final Ball getBall() {

    return ball;
  }

  /**
   * Returns the first borderAngle (where line ends and circ1 starts)
   * @return borderAngle1
   */
  public final double getBorderAngle1() {

    return borderAngle1;
  }

  /**
   * Returns the second borderAngle (where line starts and circ2 starts)
   * @return borderAngle2
   */
  public final double getBorderAngle2() {

    return borderAngle2;
  }

  /**
   * Returns the the power to reach borderAngle1
   * @return borderPower1
   */
  public final double getBorderPower1() {

    return borderPower1;
  }

  /**
   * Returns the the power to reach borderAngle2
   * @return borderPower2
   */
  public final double getBorderPower2() {

    return borderPower2;
  }

  /**
   * If borderpower1 is under 100 and actionType is not null, circ1 is returned. Otherwise null will be returned.
   * @return circ1
   */
  public final CircleSegment getCirc1() {

    if (actionType != null && borderPower1 < 100) {
      return circ1;
    }
    return null;
  }

  /**
   * If borderpower2 is over -100 and actionType is tackle, circ2 is returned. Otherwise null will be returned.
   * @return circ2
   */
  public final CircleSegment getCirc2() {

    if (actionType == ACTION_TYPE.TACKLE_ACTION && borderPower2 > -100) {
      return circ2;
    }
    return null;
  }

  /**
   * The direction of the kick/tackle
   * @return the kickDir
   */
  public final double getKickDir() {

    return kickDir;
  }

  /**
   * If actiontype is not null, the LineSegment is returned
   * @return the line
   */
  public final LineSegment getLine() {

    if (actionType != null) {
      return line;
    }
    return null;
  }

  /**
   * The shooting player is returned
   * @return the shooting player
   */
  public final Player getShootingPlayer() {

    return shootingPlayer;
  }

  /**
   * Returns a new object that is calculated for one cycle more than the actual
   * object.
   * 
   * @return the extrapolated PossibleBalls
   */
  public final PossibleBallsAfterDirectedAcc extrapolate() {

    PossibleBallsAfterDirectedAcc pb = new PossibleBallsAfterDirectedAcc(this);
    pb.extrapolateThis();
    return pb;
  }

  /**
   * Calculates the values for the next cycle.
   */
  public final void extrapolateThis() {

    if (getCirc1() != null) {
      circ1.setRadius((circ1.getRadius() / Ball
          .relativeBallMovementAfterNCycles(simulatedCycle))
          * Ball.relativeBallMovementAfterNCycles(simulatedCycle + 1));
    }
    if (getCirc2() != null) {
      circ2.setRadius((circ2.getRadius() / Ball
          .relativeBallMovementAfterNCycles(simulatedCycle))
          * Ball.relativeBallMovementAfterNCycles(simulatedCycle + 1));
    }
    dummyVektor.setVektor(ball.getDistance(line.getPoint1()), ball.getAngleTo(line.getPoint1()));
    dummyVektor.setLength((dummyVektor.getLength() / Ball
        .relativeBallMovementAfterNCycles(simulatedCycle))
        * Ball.relativeBallMovementAfterNCycles(simulatedCycle + 1));
    dummyVektor.addToThis(ball.getPosition());
    line.copyPoint1(dummyVektor);
    dummyVektor.setVektor(ball.getDistance(line.getPoint2()), borderAngle1);
    dummyVektor.setLength((dummyVektor.getLength() / Ball
        .relativeBallMovementAfterNCycles(simulatedCycle))
        * Ball.relativeBallMovementAfterNCycles(simulatedCycle + 1));
    dummyVektor.addToThis(ball.getPosition());
    line.copyPoint2(dummyVektor);
    simulatedCycle++;
  }
  
  /**
   * Calculates the needed power to shoot the ball into the given relative direction.
   * Returns Double.MAX_VALUE if no such shot is possible.
   * 
   * @param angle the wanted angle
   * @param rel relative to body or head or absolute
   * @return the needed power
   */
  public final double getPowerForShotAngleRel(final double angle,
      final RELATIVE rel) {

    if (rel == RELATIVE.TO_BODY) {
      return getPowerForShotAngleAbs(shootingPlayer.getAngleAbs(angle, RELATIVE.TO_BODY));
    }
    if (rel == RELATIVE.TO_HEAD) {
      return getPowerForShotAngleAbs(shootingPlayer.getAngleAbs(angle, RELATIVE.TO_HEAD));
    }
    return getPowerForShotAngleAbs(angle);
  }

  /**
   * Calculates the needed power to shoot the ball into the given absolute direction.
   * Returns Double.MAX_VALUE if no such shot is possible.
   * 
   * @param angle the wanted direction
   * @return the needed power
   */
  public final double getPowerForShotAngleAbs(final double angle) {

    dummyLine.copyPoint1(ball.getPosition());
    dummyLine.setPolar((SConf.getInstance().BALL_SPEED_MAX + 0.1)
        * Ball.relativeBallMovementAfterNCycles(simulatedCycle), angle);
    dummyLine.getIntersectionPoint(line, dummyVektor);
    if (!dummyVektor.isEmpty()) {
      double fact = dummyVektor.getDistance(line.getPoint2()) / line.getLength();
      return borderPower1 - (borderPower1 - borderPower2) * fact;
    }
    if (getCirc1() != null) {
      if (circ1.includesAngle(angle)) {
        double angDist = circ1.getAngleDiff();
        double dist2Angle = circ1.getDistToAngle(angle);
        double step = (100 - borderPower1) * dist2Angle / angDist;
        return circ1Starts ? borderPower1 + step : 100 - step;
      }
    }
    if (getCirc2() != null) {
      if (circ2.includesAngle(angle)) {
        double angDist = circ2.getAngleDiff();
        double dist2Angle = circ2.getDistToAngle(angle);
        double step = (-100 - borderPower2) * dist2Angle / angDist;
        return circ2Starts ? borderPower2 + step : -100 - step;
      }
    }
    return Double.MAX_VALUE;
  }

  private final void calcMinMaxAng() {
    double angle1= 0;
    if (getCirc1() != null) {
      angle1= circ1Starts?circ1.getEndAngle():circ1.getStartAngle();
    }
    else {
      angle1= borderAngle1;
    }
    double angle2= 0;
    if (actionType == ACTION_TYPE.TACKLE_ACTION) {
      if (getCirc2() != null) {
        angle2= circ2Starts?circ2.getEndAngle():circ2.getStartAngle();
      }
      else {
        angle2= borderAngle2;
      }      
    }
    else {
      angle2= ball.getAngleTo(line.getPoint1());
    }
    if (Vektor.getAngleDistance(angle1, angle2) > 180) {
      minAng= angle2;
      maxAng= angle1;
    }
    else {
      minAng= angle1;
      maxAng= angle2;
    }
  }

  /**
   * Returns the angle at most left of the ball speed that can be reached
   * 
   * @return the min angle
   */
  public final double getMinAngle() {

    return minAng;
  }

  /**
   * Returns the angle at most right of the ball speed that can be reached
   * 
   * @return the max angle
   */
  public final double getMaxAngle() {

    return maxAng;
  }

  /**
   * Calculates the point that the ball reaches when it was kicked into the given (resulting) direction.
   * returns null if the ball cannot be kicked into the given direction.
   * 
   * @param angle the angle
   * @return the point that is reached
   */
  public final Vektor getPointForShotAngleAbs(double angle) {

    if (angle == minAng) {
      angle = minAng + 0.0001;
    }
    if (angle == maxAng) {
      angle = maxAng - 0.0001;
    }
    dummyLine.copyPoint1(ball.getPosition());
    dummyLine.setPolar((SConf.getInstance().BALL_SPEED_MAX + 0.1)
        * Ball.relativeBallMovementAfterNCycles(simulatedCycle), angle);
    dummyLine.getIntersectionPoint(line, dummyVektor);
    if (!dummyVektor.isEmpty()) {
      return dummyVektor.cloned();
    }
    if (getCirc1() != null) {
      if (circ1.includesAngle(angle)) {
        try {
          return circ1.getIntersectionPoints(dummyLine).getFirst();
        }
        catch (Exception e) {
          System.err.println("circ1: " + circ1);
          System.err.println("dummyLine: " + dummyLine);
          System.err.println("angle: " + angle);
          e.printStackTrace();
        }
      }
    }
    if (getCirc2() != null) {
      if (circ2.includesAngle(angle)) {
        try {
          return circ2.getIntersectionPoints(dummyLine).getFirst();
        }
        catch (Exception e) {
          System.err.println("circ2: " + circ2);
          System.err.println("dummyLine: " + dummyLine);
          System.err.println("angle: " + angle);
          e.printStackTrace();
        }
      }
    }
    return null;
  }

  /**
   * Add the intersection points between this object and a LineSegment to the
   * given list.
   * 
   * @param intersect
   *          the LineSegment to intersect with
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final LineSegment intersect,
      final LinkedList<Vektor> list) {

    line.addIntersectionPointToList(intersect, list);
    if (getCirc1() != null) {
      circ1.addIntersectionPointsToList(intersect, list);
    }
    if (getCirc2() != null) {
      circ2.addIntersectionPointsToList(intersect, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a
   * LineSegment.
   * 
   * @param intersect
   *          the LineSegment to intersect with
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final LineSegment intersect) {

    LinkedList<Vektor> res = new LinkedList<Vektor>();
    addIntersectionPointsToList(intersect, res);
    return res;
  }

  /**
   * Add the intersection points between this object and a StraightLine to the
   * given list.
   * 
   * @param intersect
   *          the StraightLine to intersect with
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final StraightLine intersect,
      final LinkedList<Vektor> list) {

    line.addIntersectionPointToList(intersect, list);
    if (getCirc1() != null) {
      circ1.addIntersectionPointsToList(intersect, list);
    }
    if (getCirc2() != null) {
      circ2.addIntersectionPointsToList(intersect, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a
   * StraightLine.
   * 
   * @param intersect
   *          the StraightLine to intersect with
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final StraightLine intersect) {

    LinkedList<Vektor> res = new LinkedList<Vektor>();
    addIntersectionPointsToList(intersect, res);
    return res;
  }

  /**
   * Add the intersection points between this object and a CircleSegment to the
   * given list.
   * 
   * @param intersect
   *          the CircleSegment to intersect with
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final CircleSegment intersect,
      final LinkedList<Vektor> list) {

    line.addIntersectionPointsToList(intersect, list);
    if (getCirc1() != null) {
      circ1.addIntersectionPointsToList(intersect, list);
    }
    if (getCirc2() != null) {
      circ2.addIntersectionPointsToList(intersect, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a
   * CircleSegment.
   * 
   * @param intersect
   *          the CircleSegment to intersect with
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final CircleSegment intersect) {

    LinkedList<Vektor> res = new LinkedList<Vektor>();
    addIntersectionPointsToList(intersect, res);
    return res;
  }

  /**
   * Add the intersection points between this object and a Circle to the given
   * list.
   * 
   * @param intersect
   *          the Circle to intersect with
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final Circle intersect,
      final LinkedList<Vektor> list) {

    line.addIntersectionPointsToList(intersect, list);
    if (getCirc1() != null) {
      circ1.addIntersectionPointsToList(intersect, list);
    }
    if (getCirc2() != null) {
      circ2.addIntersectionPointsToList(intersect, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a Circle.
   * 
   * @param intersect
   *          the Circle to intersect with
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final Circle intersect) {

    LinkedList<Vektor> res = new LinkedList<Vektor>();
    addIntersectionPointsToList(intersect, res);
    return res;
  }

  /**
   * Add the intersection points between this object and a ReachableArea to the
   * given list.
   * 
   * @param intersect
   *          the Circle to intersect with
   * @param back
   *          true if backwards movement is allowed
   * @param list
   *          the list to add the intersection points
   */
  public final void addIntersectionPointsToList(final ReachableArea intersect,
      final boolean back,
      final LinkedList<Vektor> list) {

    line.addIntersectionPointsToList(intersect, back, list);
    if (getCirc1() != null) {
      circ1.addIntersectionPointsToList(intersect, back, list);
    }
    if (getCirc2() != null) {
      circ2.addIntersectionPointsToList(intersect, back, list);
    }
  }

  /**
   * Calculates a list of intersection points between this object and a
   * ReachableArea.
   * 
   * @param intersect
   *          the Circle to intersect with
   * @param back
   *          true if backwards movement is allowed
   * @return a list of intersection points
   */
  public final LinkedList<Vektor> getIntersectionPoints(final ReachableArea intersect,
      final boolean back) {

    LinkedList<Vektor> res = new LinkedList<Vektor>();
    addIntersectionPointsToList(intersect, back, res);
    return res;
  }
  
  /**
   * Returns the highest possible speed that the ball can have when it was kicked to the given direction.
   * @param startArea start angle
   * @param endArea end angle
   * @return the speed
   */
  public final double getHighestSpeedBetweenAngles(final double startArea, final double endArea) {
    if (!Vektor.isAngleBetween(minAng, startArea, maxAng)) {
      if (!Vektor.isAngleBetween(minAng, endArea, maxAng)) {
        if (!Vektor.isAngleBetween(startArea, minAng, endArea)) {
          return 0;
        } 
      }
    }
    // at least one angle is possible
    Vektor v1= getPointForShotAngleAbs(startArea);
    Vektor v2= getPointForShotAngleAbs(endArea);
    if (v1 != null && v2 != null) {
      double dist1= ball.getDistance(v1)/Ball.relativeBallMovementAfterNCycles(simulatedCycle);
      double dist2= ball.getDistance(v2)/Ball.relativeBallMovementAfterNCycles(simulatedCycle);
      return dist1>dist2?dist1:dist2;
    }
    if (v1== null && v2 == null) {
      v1= getPointForShotAngleAbs(minAng+0.0000001);
      v2= getPointForShotAngleAbs(maxAng-0.0000001);
      double dist1= ball.getDistance(v1)/Ball.relativeBallMovementAfterNCycles(simulatedCycle);
      double dist2= ball.getDistance(v2)/Ball.relativeBallMovementAfterNCycles(simulatedCycle);
      return dist1>dist2?dist1:dist2;      
    }
    if (v1 == null) {
      v1= getPointForShotAngleAbs(minAng+0.0000005);
      double dist1= ball.getDistance(v1)/Ball.relativeBallMovementAfterNCycles(simulatedCycle);
      double dist2= ball.getDistance(v2)/Ball.relativeBallMovementAfterNCycles(simulatedCycle);
      return dist1>dist2?dist1:dist2;      
    }
    v2= getPointForShotAngleAbs(maxAng-0.0000005);
    double dist1= ball.getDistance(v1)/Ball.relativeBallMovementAfterNCycles(simulatedCycle);
    double dist2= ball.getDistance(v2)/Ball.relativeBallMovementAfterNCycles(simulatedCycle);
    return dist1>dist2?dist1:dist2;      
  }

  /** (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  public final String toString() {

    StringBuffer sb = new StringBuffer();
    sb.append("AC_TYPE: ").append(actionType).append(" cyc: ").append(
        simulatedCycle).append(" minAng: ").append(minAng).append(" maxAng: ")
        .append(maxAng).append(" borderAng1: ").append(borderAngle1).append(
            " borderAng2: ").append(borderAngle2).append(" line: ").append(
            getLine()).append(" circ1: ").append(getCirc1()).append(" circ2: ")
        .append(getCirc2());
    return sb.toString();
  }

}
